﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Chefbook.DataObjects;
using System.Collections.Specialized;

namespace UnitTests.DataObjectsTests
{
    [TestFixture]
    public class CategoryFixture
    {
        private Category root;
        private Category testCategory;

        [SetUp]
        public void Init()
        {
            root = new Category(null);
            root.Name = "Root";
            testCategory = new Category(root);
        }

        [Test]
        public void ConstructorTest()
        {
            Assert.AreEqual(root, testCategory.Parent, "Constructor Parent test failed");
            Assert.AreEqual(1, testCategory.Parent.Subcategories.Count((c) => c == testCategory), "Parent's Subs doesn't contain category exactly once");
            Assert.AreEqual("", testCategory.Name, "Constructor Name test failed");
            Assert.AreEqual(0, testCategory.Subcategories.Count, "Constructor SubCategories test failed");
            Assert.AreEqual(0, testCategory.DirectRecipes.Count, "Constructor Recipes test failed");
        }

        [Test]
        public void RenameTest()
        {
            testCategory.AssertPropertyChangedFired(() => testCategory.Name = "Tasty", "Name");
            Assert.AreEqual("Tasty", testCategory.Name, "Rename test failed");
        }

        [Test]
        public void RenameNullTest()
        {
            Assert.Throws<ArgumentNullException>(() => testCategory.Name = null);
        }

        [Test]
        public void DeleteTest()
        {
            var parent = testCategory.Parent;
            new Recipe(testCategory);
            new Recipe(testCategory);
            testCategory.Delete();
            Assert.IsFalse(parent.Subcategories.Contains(testCategory), "Delete test failed");
            foreach (var recipe in testCategory.DirectRecipes)
            {
                Assert.IsFalse(recipe.Categories.Contains(testCategory), "Category not removed from all recipes");
            }
        }

        [Test]
        public void ChangeParentTest()
        {
            root.Name = "Root";
            testCategory.Name = "Test";
            var oldParent = testCategory.Parent;
            var newParent = new Category(root);
            newParent.Name = "NewParent";
            testCategory.AssertPropertyChangedFired(() => testCategory.Parent = newParent, "Parent");
            Assert.IsFalse(oldParent.Subcategories.Contains(testCategory), string.Format("{0} not removed from old parent ({1})", testCategory.Name, oldParent.Name));
            Assert.AreEqual(newParent, testCategory.Parent, "New Parent not set");
            Assert.IsTrue(newParent.Subcategories.Contains(testCategory), "Not added to new parent");
        }

        [Test]
        public void GetAllRecipesTest()
        {
            var recipe = new Recipe(testCategory);
            var subCategory = new Category(testCategory);
            var subSubCategory = new Category(subCategory);
            var nestedrecipe = new Recipe(subSubCategory);


            Assert.Contains(recipe, testCategory.AllRecipes, "AllRecipes didn't contain direct recipe");
            Assert.Contains(nestedrecipe, testCategory.AllRecipes, "AllRecipes didn't contain nested recipe");
        }

        [Test]
        public void GetDirectRecipesTest()
        {
            var recipe = new Recipe(testCategory);
            var subCategory = new Category(testCategory);
            var subSubCategory = new Category(subCategory);
            var nestedrecipe = new Recipe(subSubCategory);

            Assert.Contains(recipe, testCategory.DirectRecipes, "DirectRecipes didn't contain direct recipe");
            Assert.IsFalse(testCategory.DirectRecipes.Contains(nestedrecipe), "DirectRecipes contained nested recipe");
        }

        [Test]
        public void AddRecipeAddsToDirectRecipesTest()
        {
            var recipe = new Recipe(root);
            var args = testCategory.DirectRecipes.AssertCollectionChangedFiredAndReturnArgs(() => 
                testCategory.AddRecipe(recipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(recipe, args.NewItems);
            Assert.Contains(recipe, testCategory.DirectRecipes, "Recipe wasn't in DirectRecipes of the category");
            Assert.Contains(testCategory, recipe.Categories, "Category wasn't in recipe's list of categories");
        }

        [Test]
        public void AddRecipeAddsToAllRecipesTest()
        {
            var recipe = new Recipe(root);
            var args = testCategory.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                testCategory.AddRecipe(recipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(recipe, args.NewItems);
            Assert.Contains(recipe, testCategory.AllRecipes, "Recipe wasn't in DirectRecipes of the category");
            Assert.Contains(testCategory, recipe.Categories, "Category wasn't in recipe's list of categories");
        }

        [Test]
        public void AddRecipeToSubAddsToAllRecipesTest()
        {
            var recipe = new Recipe(root);
            var sub = new Category(testCategory);
            var args = testCategory.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                sub.AddRecipe(recipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(recipe, args.NewItems);
            Assert.Contains(recipe, testCategory.AllRecipes, "Recipe wasn't in AllRecipes of the category");
        }

        [Test]
        public void AllRecipesDoesntContainDuplicatesTest()
        {
            var recipe = new Recipe(root);
            var sub = new Category(testCategory);
            var args = testCategory.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                sub.AddRecipe(recipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(recipe, args.NewItems); 
            
            var sub2 = new Category(testCategory);
            sub2.AddRecipe(recipe);
            
            Assert.True(testCategory.AllRecipes.Count((x) => x == recipe) == 1, "Recipe wasn't in AllRecipes of the category exactly once");
        }

        [Test]
        public void AddRecipeTwiceTest()
        {
            var recipe = new Recipe(root);
            var args = testCategory.DirectRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                testCategory.AddRecipe(recipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action);
            Assert.Contains(recipe, args.NewItems);
            
            testCategory.DirectRecipes.AssertCollectionChangedDidntFire(() =>
                            testCategory.AddRecipe(recipe));
         
            Assert.AreEqual(1, testCategory.DirectRecipes.Count((r) => r == recipe), "Recipe was added more than once");
            Assert.AreEqual(1, recipe.Categories.Count((c) => c == testCategory), "Category was aded to recipe more than once");
        }

        [Test]
        public void AddRecipeNullTest()
        {
            Assert.Throws<ArgumentNullException>(() => testCategory.AddRecipe(null));
        }

        [Test]
        public void RemoveRecipeTest()
        {
            var recipe = new Recipe(root);
            testCategory.AddRecipe(recipe);
            var args = testCategory.DirectRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                testCategory.RemoveRecipe(recipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.Contains(recipe, args.OldItems);

            Assert.False(testCategory.DirectRecipes.Contains(recipe), "Recipe wasn't removed from DirectRecipes of the category");
            Assert.False(recipe.Categories.Contains(testCategory), "Category wasn't removed from recipe's list of categories");
        }

        [Test]
        public void RemoveRecipeRemovesFromAllRecipesTest()
        {
            var recipe = new Recipe(root);
            testCategory.AddRecipe(recipe);
            var args = testCategory.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                testCategory.RemoveRecipe(recipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.Contains(recipe, args.OldItems);

            Assert.False(testCategory.AllRecipes.Contains(recipe), "Recipe wasn't removed from DirectRecipes of the category");
            Assert.False(recipe.Categories.Contains(testCategory), "Category wasn't removed from recipe's list of categories");
        }

        [Test]
        public void RemoveRecipeRemovesFromParentsAllRecipesTest()
        {
            var recipe = new Recipe(testCategory);
            var subCategory = new Category(testCategory);
            var subSubCategory = new Category(subCategory);
            var nestedrecipe = new Recipe(subSubCategory);

            var args = testCategory.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                subSubCategory.RemoveRecipe(nestedrecipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.Contains(nestedrecipe, args.OldItems);

            Assert.False(testCategory.AllRecipes.Contains(nestedrecipe), "Recipe wasn't removed from DirectRecipes of the category");
        }

        [Test]
        public void RemoveRecipeDoesntRemoveFromParentsAllRecipesIfContainedElsewhereTest1()
        {
            var recipe = new Recipe(testCategory);
            var subCategory = new Category(testCategory);
            var subSubCategory = new Category(subCategory);
            subSubCategory.AddRecipe(recipe);

            var args = subSubCategory.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                subSubCategory.RemoveRecipe(recipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.Contains(recipe, args.OldItems);

            Assert.True(testCategory.AllRecipes.Contains(recipe), "testCategory should still contain recipe because it's in the direct recipes");
            Assert.False(recipe.Categories.Contains(subSubCategory), "Category wasn't removed from recipe's list of categories");
        }

        [Test]
        public void RemoveRecipeDoesntRemoveFromParentsAllRecipesIfContainedElsewhereTest2()
        {
            var recipe = new Recipe(testCategory);
            var subCategory = new Category(testCategory);
            var newRecipe = new Recipe(subCategory);
            var subSubCategory = new Category(subCategory);
            subSubCategory.AddRecipe(newRecipe);

            var args = subSubCategory.AllRecipes.AssertCollectionChangedFiredAndReturnArgs(() =>
                subSubCategory.RemoveRecipe(newRecipe));
            Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action);
            Assert.Contains(newRecipe, args.OldItems);

            Assert.True(testCategory.AllRecipes.Contains(recipe), "testCategory should still contain recipe because it's in the direct recipes");
            Assert.False(recipe.Categories.Contains(subSubCategory), "Category wasn't removed from recipe's list of categories");
        }

        [Test]
        public void RemoveRecipeNullTest()
        {
            Assert.Throws<ArgumentNullException>(() => testCategory.RemoveRecipe(null));
        }

        [Test]
        public void RemoveNotFoundRecipeTest()
        {
            var recipe = new Recipe(root);
            Assert.Throws<ArgumentException>(() => testCategory.RemoveRecipe(recipe));
        }

        [Test]
        public void AddSubCategoryTest()
        {
            Category sub = new Category();
            testCategory.AddSubcategory(sub);
            Assert.Contains(sub, testCategory.Subcategories);
            Assert.AreEqual(testCategory, sub.Parent);
            Assert.AreEqual(1, testCategory.Subcategories.Count);
            testCategory.AddSubcategory(sub);
            Assert.AreEqual(1, testCategory.Subcategories.Count);
        }

        [Test]
        public void AddNullSubcategoryTest()
        {
            Assert.Throws<ArgumentNullException>(() => testCategory.AddSubcategory(null));
        }

        [Test]
        public void RemoveSubcategoryTest()
        {
            Category sub = new Category();
            testCategory.AddSubcategory(sub);
            testCategory.RemoveSubcategory(sub);
            Assert.IsNull(sub.Parent);
            Assert.AreEqual(0, testCategory.Subcategories.Count);
        }

        [Test]
        public void RemoveNullSubcategoryTest()
        {
            Assert.Throws<ArgumentNullException>(() => testCategory.RemoveSubcategory(null));
        }

        [Test]
        public void RemoveNonExistingSubcategoryTest()
        {
            Category sub = new Category();
            Assert.Throws<ArgumentException>(() => testCategory.RemoveSubcategory(sub));
        }

        [Test]
        public void EqualsTest()
        {
            Category tasties = new Category(null) { Name = "Tasties" };
            Category pasta = new Category(tasties) { Name = "Pasta" };
            Category chicken = new Category(pasta) { Name = "Chicken" };
            Recipe chickenShrimpScampi = new Recipe(chicken) { Name = "Chicken and Shrimp Scampi" };
            Recipe chickenFettuciniAlfredo = new Recipe(chicken) { Name = "Chicken Fettucini Alfredo" };
            Category beef = new Category(pasta) { Name = "Chicken" };
            Recipe spaghettiAndMeatballs = new Recipe(beef) { Name = "Spaghetti and Meatballs" };
            Recipe lasagna = new Recipe(beef) { Name = "Lasagna" };
            Category pork = new Category(pasta) { Name = "Pork" };
            pork.AddRecipe(lasagna);
            Category seafood = new Category(pasta) { Name = "Seafood" };
            seafood.AddRecipe(chickenShrimpScampi);

            var FirstCategory = tasties;

            tasties = new Category(null) { Name = "Tasties" };
            pasta = new Category(tasties) { Name = "Pasta" };
            chicken = new Category(pasta) { Name = "Chicken" };
            chickenShrimpScampi = new Recipe(chicken) { Name = "Chicken and Shrimp Scampi" };
            chickenFettuciniAlfredo = new Recipe(chicken) { Name = "Chicken Fettucini Alfredo" };
            beef = new Category(pasta) { Name = "Chicken" };
            spaghettiAndMeatballs = new Recipe(beef) { Name = "Spaghetti and Meatballs" };
            lasagna = new Recipe(beef) { Name = "Lasagna" };
            pork = new Category(pasta) { Name = "Pork" };
            pork.AddRecipe(lasagna);
            seafood = new Category(pasta) { Name = "Seafood" };
            seafood.AddRecipe(chickenShrimpScampi);

            var SecondCategory = tasties;

            Assert.True(FirstCategory.ValueEquals(SecondCategory));
        }

        [Test]
        public void NotEqualsTest1()
        {
            Category tasties = new Category(null) { Name = "Tasties" };
            Category pasta = new Category(tasties) { Name = "Pasta" };
            Category chicken = new Category(pasta) { Name = "Chicken" };
            Recipe chickenShrimpScampi = new Recipe(chicken) { Name = "Chicken and Shrimp Scampi" };
            Recipe chickenFettuciniAlfredo = new Recipe(chicken) { Name = "Chicken Fettucini Alfredo" };
            Category beef = new Category(pasta) { Name = "Chicken" };
            Recipe spaghettiAndMeatballs = new Recipe(beef) { Name = "Spaghetti and Meatballs" };
            Recipe lasagna = new Recipe(beef) { Name = "Lasagna" };
            Category pork = new Category(pasta) { Name = "Pork" };
            pork.AddRecipe(lasagna);
            Category seafood = new Category(pasta) { Name = "Seafood" };
            seafood.AddRecipe(chickenShrimpScampi);

            var FirstCategory = tasties;

            tasties = new Category(null) { Name = "Tasties" };
            pasta = new Category(tasties) { Name = "Pasta" };
            chicken = new Category(pasta) { Name = "Chicken" };
            chickenShrimpScampi = new Recipe(chicken) { Name = "Chicken and Shrimp Scampi" };
            chickenFettuciniAlfredo = new Recipe(chicken) { Name = "Chicken Fettucini Alfredo" };
            beef = new Category(pasta) { Name = "Chicken" };
            spaghettiAndMeatballs = new Recipe(beef) { Name = "Spaghetti and Meatballs" };
            lasagna = new Recipe(beef) { Name = "Lasagna", Description = "Hello" };
            pork = new Category(pasta) { Name = "Pork" };
            pork.AddRecipe(lasagna);
            seafood = new Category(pasta) { Name = "Seafood" };
            seafood.AddRecipe(chickenShrimpScampi);

            var SecondCategory = tasties;

            Assert.False(FirstCategory.ValueEquals(SecondCategory));
        }

        [Test]
        public void NotEqualsTest2()
        {
            Category tasties = new Category(null) { Name = "Tasties" };
            Category pasta = new Category(tasties) { Name = "Pasta" };
            Category chicken = new Category(pasta) { Name = "Chicken" };
            Recipe chickenShrimpScampi = new Recipe(chicken) { Name = "Chicken and Shrimp Scampi" };
            Recipe chickenFettuciniAlfredo = new Recipe(chicken) { Name = "Chicken Fettucini Alfredo" };
            Category beef = new Category(pasta) { Name = "Chicken" };
            Recipe spaghettiAndMeatballs = new Recipe(beef) { Name = "Spaghetti and Meatballs" };
            Recipe lasagna = new Recipe(beef) { Name = "Lasagna" };
            Category pork = new Category(pasta) { Name = "Pork" };
            pork.AddRecipe(lasagna);
            Category seafood = new Category(pasta) { Name = "Seafood" };
            seafood.AddRecipe(chickenShrimpScampi);

            var FirstCategory = tasties;

            tasties = new Category(null) { Name = "Tasties" };
            pasta = new Category(tasties) { Name = "Pasta" };
            chicken = new Category(pasta) { Name = "Chicken" };
            chickenShrimpScampi = new Recipe(chicken) { Name = "Chicken and Shrimp Scampi" };
            chickenFettuciniAlfredo = new Recipe(chicken) { Name = "Chicken Fettucini Alfredo" };
            beef = new Category(pasta) { Name = "Chicken" };
            spaghettiAndMeatballs = new Recipe(beef) { Name = "Spaghetti and Meatballs" };
            lasagna = new Recipe(beef) { Name = "Lasagna" };
            pork = new Category(pasta) { Name = "Pork" };
            pork.AddRecipe(lasagna);
            seafood = new Category(pasta) { Name = "Seafood" };
            seafood.AddRecipe(chickenShrimpScampi);
            Category veggie = new Category(pasta) { Name = "Veggie" };

            var SecondCategory = tasties;

            Assert.False(FirstCategory.ValueEquals(SecondCategory));
        }
    }
}